package org.jeecg.modules.demo.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.jeecg.modules.demo.weixin.util.PayConstants;
import org.springframework.util.Base64Utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.List;

/**
 * @Author jeecg-boot
 * @Date 2023/6/29 16:00
 * @Description
 */
@Slf4j
public class VechatPayV3Util {

    /**
     * 加密姓名
     * @return
     */
    @SneakyThrows
    public static String encryption(String realName,String zhengshu) {
        String keyPath = PayConstants.API_PROGIT_SHARING;
        //获取认证信息
//        String strToken = VechatPayV3Util.getToken("POST", HttpUrl.parse("https://api.mch.weixin.qq.com/v3/transfer/batches"), JSONObject.toJSONString("paramWxTransferAccounts"), keyPath);
//        log.info("获取认证信息:" + strToken);

        //随机字符串
        String nonce_str = RandomUtil.randomString(32);
        //时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        String body = "";
        //获取签名
        String orgSignText = "GET\n"
                + "/v3/certificates\n"
                + timestamp + "\n"
                + nonce_str + "\n"
                + body + "\n";
        String signStr = VechatPayV3Util.sign(orgSignText.getBytes("utf-8"), keyPath);
        log.info("签名信息:" + signStr);
        X509Certificate certificate = getCertificate(FileUtil.getInputStream(PayConstants.API_PROGITS_SHARING));
        String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
        //构造签名串
        String auth = "WECHATPAY2-SHA256-RSA2048 "
                + "mchid=\"" + PayConstants.MCH_ID_SERVICE + "\",nonce_str=\""
                + nonce_str + "\",timestamp=\"" + timestamp
                + "\",serial_no=\"" + serialNo + "\",signature=\"" + signStr + "\"";
        log.info("签名串:" + auth);
//        "6939EC3A245CD46E49F20975753C56AF4AD63E64"
        //获取微信支付平台公钥证书
        String platformPublicKey = HttpUtil.sendGetRequest("https://api.mch.weixin.qq.com/v3/certificates", auth, null);
        log.info("微信支付平台公钥证书:" + platformPublicKey);

        JSONObject parse = JSONObject.parseObject(platformPublicKey, JSONObject.class);
        Object data = parse.get("data");
        List<Object> list = (List<Object>) data;
        JSONObject json = (JSONObject) JSONObject.toJSON(list.get(0));
        json.get("");
        json.get("serial_no");
        WxPublicKeyData wxPublicKeyData = new WxPublicKeyData();
        wxPublicKeyData.setEffectiveTime(json.get("effective_time").toString());
        wxPublicKeyData.setEncryptCertificate((JSONObject) JSONObject.toJSON(json.get("encrypt_certificate")));
        wxPublicKeyData.setExpireTime(json.get("expire_time").toString());
        wxPublicKeyData.setSerialNo(json.get("serial_no").toString());
        //转为json对象进行解密
        //解密
        String publicKey = decryptResponseBody(wxPublicKeyData);
        log.info("解密后的数据:" + publicKey);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
        log.info("ByteArrayInputStream：" + inputStream);

        X509Certificate certificate2 = getCertificate(inputStream);
//        String encode = URLEncoder.encode(,"Utf-8");
        //加密隐私信息  这里我用来加密转账所需的姓名
        return rsaEncryptOAEP(realName, certificate2);

    }

    //隐私信息加密
    public static String rsaEncryptOAEP(String message, X509Certificate certificate) throws IllegalBlockSizeException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());

            byte[] data = message.getBytes("utf-8");
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.getEncoder().encodeToString(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }




    /**
     * 获取证书
     *
     * @param inputStream 证书文件
     * @return {@link X509Certificate} 获取证书
     */
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }


    //使用apiV3的密钥进行解密响应体
    public static String decryptResponseBody(WxPublicKeyData tempWxpublicKeyData) {

//        tempWxpublicKeyData 这个对象就是取回来的公钥字符串转换的，有时回取回多条公钥，取时间最新的
        String apiV3Key= PayConstants.API_V2KEY_SERVICE;
        String associatedData=tempWxpublicKeyData.getEncryptCertificate().get("associated_data").toString();
        String nonce=tempWxpublicKeyData.getEncryptCertificate().get("nonce").toString();
        String ciphertext=tempWxpublicKeyData.getEncryptCertificate().get("ciphertext").toString();

        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");

            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));

            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @SneakyThrows
    public static String getToken(String method, HttpUrl url, String body, String keyPath) {
        String nonceStr = RandomUtil.randomString(32);
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"), keyPath);

        return "mchid=\"" + PayConstants.MCH_ID_SERVICE + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + PayConstants.MCH_SERIAL_NO + "\","
                + "signature=\"" + signature + "\"";
    }
//"6939EC3A245CD46E49F20975753C56AF4AD63E64"
    @SneakyThrows
    public static String sign(byte[] message, String keyPath) {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(keyPath));
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }

    public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }

        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }


    /**
     * 解码PrivateKey
     *
     * @return
     */
    @SneakyThrows
    public static PrivateKey getPrivateKey(String filename) {

        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            //System.out.println("--------privateKey---------:"+privateKey);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }


}
